package cate.game.pvp.eliterace;

import cate.common.table.d.GDEliteRace;
import cate.game.event.eliterace.EliteRaceTimerEvent;
import cate.game.framework.ModuleMan;
import cate.game.framework.advanced.AdvancedByteAttributes;
import cate.game.framework.timer.FIFOTimerSelector;
import cate.game.framework.timer.FIFOTimerTask;
import cate.game.framework.timer.TimerTask;
import cate.game.pvp.eliterace.data.EliteRaceKV;
import cate.game.pvp.eliterace.timer.EliteRaceTimerTask;
import cate.game.pvp.eliterace.timer.EliteRaceTimerTask.Settings;
import easy.java.dev.note.NoteField;
import org.apache.commons.lang3.time.DateUtils;

import java.util.LinkedList;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static cate.game.framework.advanced.AdvancedEventSource.LOCAL;
import static cate.game.pvp.eliterace.data.EliteRaceKV.OP_SKIP_SEASON;

public class EliteRaceTimerModule extends ModuleMan
        implements EliteRaceTimerEvent.Creator, EliteRaceTimerTask.Creator {
    @NoteField(value = "时间映射为任务的队列")
    private FIFOTimerSelector timerSelector;

    @NoteField(value = "精英赛所有数据")
    private final EliteRaceDataManager dataManager;

    private final EliteRaceTimerTask.SettingsFunctionSelector settingsFunctionSelector;

    public EliteRaceTimerModule(Supplier<EliteRaceDataManager> dataManagerSupplier,
                                Supplier<EliteRaceTimerTask.SettingsFunctionSelector> settingsFunctionSelectorSupplier) {
        this.dataManager = Objects.requireNonNull(dataManagerSupplier.get());
        this.settingsFunctionSelector = Objects.requireNonNull(settingsFunctionSelectorSupplier.get());
    }

    @Override
    public void initDependent() throws Exception {
        this.timerSelector = new FIFOTimerSelector(
                buildTimerTaskList(),
                this::getCycleTimeMs
        );
        this.timerSelector.log();
    }

    /**
     * 获取循环时间
     */
    private Long getCycleTimeMs() {
        return dataManager.getData().getSeason() % 2 == 1 ?
                GDEliteRace.WHOLE_SEASON_TIME : GDEliteRace.WHOLE_SEASON_TIME + DateUtils.MILLIS_PER_DAY;
    }

    public void tick() {
        Optional.of(this.timerSelector)
                .map(selector -> selector.select(System.currentTimeMillis()))
                .ifPresent(TimerTask::run);
    }

    @Override
    public EliteRaceTimerEvent createTimerEvent(Settings settings) {
        EliteRaceTimerEvent timerEvent = new EliteRaceTimerEvent();
        timerEvent.setSeason(settings.getSpec().getSeason());
        timerEvent.setGameBody(game);
        timerEvent.setEventSource(LOCAL);
        timerEvent.setEventType(settings.getEventType());
        timerEvent.setExecutedTime(settings.getTriggerTimeMs());
        switch (settings.getEventType()) {
            case OPEN:
                timerEvent.setOpenEvent(buildOpenEvent(settings));
                break;
            case SWITCH:
                timerEvent.setSwitchEvent(buildSwitchEvent(settings));
                break;
            case RANK_SETTLE:
                timerEvent.setRankSettleEvent(buildRankSettleEvent(settings));
                break;
            case INVITATION:
                timerEvent.setInvitationEvent(buildInvitationEvent(settings));
                break;
            case TRIAL_GROUP:
                timerEvent.setTrialGroupEvent(buildTrialGroupEvent(settings));
                break;
            case TRIAL:
                timerEvent.setTrialEvent(buildTrialEvent(settings));
                break;
            case KO_GROUP:
                timerEvent.setKoGroupEvent(buildKoGroupEvent(settings));
                break;
            case KO:
                timerEvent.setKoEvent(buildKoEvent(settings));
                break;
            case FINAL_GROUP:
                timerEvent.setFinalGroupEvent(buildFinalGroupEvent(settings));
                break;
            case FINAL:
                timerEvent.setFinalEvent(buildFinalEvent(settings));
                break;
            case RANK_SWITCH:
                timerEvent.setRankSwitchEvent(buildRankSwitchEvent(settings));
                break;
            case SHOW:
                timerEvent.setShowEvent(buildShowEvent(settings));
                break;
            case REST:
                timerEvent.setRestEvent(buildRestEvent(settings));
                break;
            default:
                break;
        }
        return timerEvent;
    }

    private EliteRaceTimerEvent.RestEvent buildRestEvent(Settings settings) {
        EliteRaceTimerEvent.RestEvent restEvent = new EliteRaceTimerEvent.RestEvent();
        restEvent.setSpec(settings.getSpec());
        return restEvent;
    }

    private EliteRaceTimerEvent.ShowEvent buildShowEvent(Settings settings) {
        EliteRaceTimerEvent.ShowEvent showEvent = new EliteRaceTimerEvent.ShowEvent();
        showEvent.setSpec(settings.getSpec());
        return showEvent;
    }

    private EliteRaceTimerEvent.RankSettleEvent buildRankSettleEvent(Settings settings) {
        EliteRaceTimerEvent.RankSettleEvent rankSettleEvent = new EliteRaceTimerEvent.RankSettleEvent();
        rankSettleEvent.setSpec(settings.getSpec());
        return rankSettleEvent;
    }

    private EliteRaceTimerEvent.InvitationEvent buildInvitationEvent(Settings settings) {
        EliteRaceTimerEvent.InvitationEvent invitationEvent = new EliteRaceTimerEvent.InvitationEvent();
        invitationEvent.setSpec(settings.getSpec());
        return invitationEvent;
    }

    private EliteRaceTimerEvent.SwitchEvent buildSwitchEvent(Settings settings) {
        dataManager.clear();
        EliteRaceTimerEvent.SwitchEvent switchEvent = new EliteRaceTimerEvent.SwitchEvent();
        switchEvent.setSpec(settings.getSpec());
        switchEvent.setSwitchTime(settings.getTriggerTimeMs());
        switchEvent.setFromSeason(dataManager.getData().getSeason());
        switchEvent.setToSeason(settings.getSpec().getSeason());
        return switchEvent;
    }

    private EliteRaceTimerEvent.RankSwitchEvent buildRankSwitchEvent(Settings settings) {
        EliteRaceTimerEvent.RankSwitchEvent rankSwitchEvent = new EliteRaceTimerEvent.RankSwitchEvent();
        rankSwitchEvent.setSpec(settings.getSpec());
        return rankSwitchEvent;
    }

    private EliteRaceTimerEvent.FinalEvent buildFinalEvent(Settings settings) {
        EliteRaceTimerEvent.FinalEvent finalEvent = new EliteRaceTimerEvent.FinalEvent();
        finalEvent.setSpec(settings.getSpec());
        return finalEvent;
    }

    private EliteRaceTimerEvent.FinalGroupEvent buildFinalGroupEvent(Settings settings) {
        EliteRaceTimerEvent.FinalGroupEvent finalGroupEvent = new EliteRaceTimerEvent.FinalGroupEvent();
        finalGroupEvent.setSpec(settings.getSpec());
        return finalGroupEvent;
    }

    private EliteRaceTimerEvent.KoEvent buildKoEvent(Settings settings) {
        EliteRaceTimerEvent.KoEvent koEvent = new EliteRaceTimerEvent.KoEvent();
        koEvent.setSpec(settings.getSpec());
        return koEvent;
    }

    private EliteRaceTimerEvent.KoGroupEvent buildKoGroupEvent(Settings settings) {
        EliteRaceTimerEvent.KoGroupEvent koGroupEvent = new EliteRaceTimerEvent.KoGroupEvent();
        koGroupEvent.setSpec(settings.getSpec());
        return koGroupEvent;
    }

    private EliteRaceTimerEvent.TrialEvent buildTrialEvent(Settings settings) {
        EliteRaceTimerEvent.TrialEvent trialEvent = new EliteRaceTimerEvent.TrialEvent();
        trialEvent.setSpec(settings.getSpec());
        return trialEvent;
    }

    private EliteRaceTimerEvent.TrialGroupEvent buildTrialGroupEvent(Settings settings) {
        EliteRaceTimerEvent.TrialGroupEvent trialGroupEvent = new EliteRaceTimerEvent.TrialGroupEvent();
        trialGroupEvent.setSpec(settings.getSpec());
        return trialGroupEvent;
    }

    private EliteRaceTimerEvent.OpenEvent buildOpenEvent(Settings settings) {
        EliteRaceTimerEvent.OpenEvent openEvent = new EliteRaceTimerEvent.OpenEvent();
        openEvent.setSpec(settings.getSpec());
        openEvent.setOpenTime(settings.getTriggerTimeMs());
        openEvent.setSeason(settings.getSpec().getSeason());
        return openEvent;
    }


    @Override
    public EliteRaceTimerTask createTimerTask(Settings settings) {
        return new EliteRaceTimerTask(
                settings,
                s -> dispatchTimerEvent(createTimerEvent(s)),
                this.settingsFunctionSelector.select(settings)
        );
    }

    private void dispatchTimerEvent(EliteRaceTimerEvent timerEvent) {
        EliteRaceKV kv = dataManager.getData();
        AdvancedByteAttributes opAttributes = kv.getOpAttributes();
        if (!opAttributes.hasAttribute(OP_SKIP_SEASON)) {
            publishEvent(game, timerEvent);
        }
    }


    private LinkedList<FIFOTimerTask> buildTimerTaskList() {
        return this.dataManager.getData().getTimerSettings()
                .stream()
                .map(this::createTimerTask)
                .collect(Collectors.toCollection(LinkedList::new));
    }
}
